# Group Grids

A grid is a layout for a page or a part of the page of the OpenProject application. It defines the structure (number of rows and number of columns) as well as the contents of the page.

The contents is defined by `GridWidget`s. While a `GridWidget` is it's own type, it is not a resource in it's own right as it is an intrinsic part of a `Grid`.

Depending on what page a grid is defined for, different widgets may be eligible to be placed on the grid. The page might also define the permissions needed for accessing, creating or modifiying the grid.

Currently, the following pages employ grids:
+ /my/page: The my page every user has. Only a user can access or modify their "my page".

*The delete action is not yet supported*

## Actions
| Link                | Description                                                          | Condition                                                                                           |
|:-------------------:| -------------------------------------------------------------------- | ----------------------------------------------------------------                                    |
| updateImmediately   | Directly perform edits on this grid                                  | **Permission**: depends on the page the grid is defined for                                         |
| update              | Validate edits on the grid via a form resource before commiting      | **Permission**: depends on the page the grid is defined for                                         |

## Linked Properties
| Link          | Description                                                       | Type                | Constraints           | Supported operations | Condition                                     |
| :-----------: | --------------------------------------------------------------    | -------------       | --------------------- | -------------------- | -----------------------------------------     |
| self          | This grid                                                         | Grid                | not null              | READ                 |                                               |
| page          | The url of the page the grid is defined for                       | url                 | not null              | READ / WRITE         | The page cannot be changed after the creation |

## Local Properties

| Property     | Description                                               | Type          | Constraints                                          | Supported operations | Condition                   |
| :----------: | --------------------------------------------------------- | --------      | ---------------------------------------------------- | -------------------- | --------------              |
| id           | Grid's id                                                 | Integer       | x > 0                                                | READ                 |                             |
| rowCount     | The number of rows the grid has                           | Integer       | x > 0                                                | READ/WRITE           |                             |
| columnCount  | The number of columns the grid has                        | Integer       | x > 0                                                | READ/WRITE           |                             |
| widgets      | The set of `GridWidget`s selected for the grid            | []GridWidget  |                                                      | READ/WRITE           | The widgets cannot overlap  |
| createdAt    | The time the grid was created                             | DateTime      |                                                      | READ                 |                             |
| updatedAt    | The time the grid was last updated                        | DateTime      |                                                      | READ                 |                             |

## GridWidget Properties

| Property     | Description                                                                        | Type     | Constraints                                          | Supported operations | Condition      |
| :----------: | ---------------------------------------------------------                          | -------- | ---------------------------------------------------- | -------------------- | -------------- |
| identifier   | The kind of widget                                                                 | String   | not null                                             | READ/WRITE           |                |
| startRow     | The row the widget starts at (1 based)                                             | Integer  | x > 0, x < rowCount of the grid, x < endRow          | READ/WRITE           |                |
| endRow       | The row the widget ends. The widget's area does not include the row itself.        | Integer  | x > 0, x <= rowCount of the grid, x > startRow       | READ/WRITE           |                |
| startColumn  | The column the widget starts at (1 based)                                          | Integer  | x > 0, x < columnCount of the grid, x < endColumn    | READ/WRITE           |                |
| endColumn    | The column the widget ends. The widget's area does not include the column itself.  | Integer  | x > 0, x <= columnCount of the grid, x > startColumn | READ/WRITE           |                |
| options      | An options hash of values customizable by the widget                               | JSON     |                                                      | READ/WRITE           |                |

## Grid [/api/v3/grids/{id}]

+ Model
    + Body

            {
                "_type": "Grid",
                "id": 2,
                "rowCount": 8,
                "columnCount": 5,
                "widgets": [
                     {
                         "_type": "GridWidget",
                         "identifier": "time_entries_current_user",
                         "startRow": 1,
                         "endRow": 8,
                         "startColumn": 1,
                         "endColumn": 3
                     },
                     {
                         "_type": "GridWidget",
                         "identifier": "news",
                         "startRow": 3,
                         "endRow": 8,
                         "startColumn": 4,
                         "endColumn": 5
                     },
                     {
                         "_type": "GridWidget",
                         "identifier": "documents",
                         "startRow": 1,
                         "endRow": 3,
                         "startColumn": 3,
                         "endColumn": 6
                     }
                ],
                "createdAt": "2018-12-03T16:58:30Z",
                "updatedAt": "2018-12-13T19:36:40Z",
                "_links": {
                    "scope": {
                        "href": "/my/page",
                        "type": "text/html"
                    },
                    "updateImmediately": {
                        "href": "/api/v3/grids/2",
                        "method": "patch"
                    },
                    "update": {
                        "href": "/api/v3/grids/2/form",
                        "method": "post"
                    },
                    "self": {
                        "href": "/api/v3/grids/2"
                    }
                }
            }

## View grid [GET]

+ Parameters
    + id (required, integer, `1`) ... grid id

+ Response 200 (application/hal+json)

    [Grid][]

+ Response 404 (application/hal+json)

    Returned if the Grid does not exist or if the user does not have permission to view it.

    **Required permission** depends on the page the grid is defined for

    + Body

            {
                "_type": "Error",
                "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
                "message": "The requested resource could not be found."
            }


## Grids [/api/v3/grids{?offset,pageSize,filters,sortBy}]

+ Model
    + Body

            {
                "_type": "Collection",
                "total": 1,
                "count": 1,
                "pageSize": 30,
                "offset": 1,
                "_embedded": {
                    "elements": [
                        {
                            "_type": "Grid",
                            "id": 2,
                            "rowCount": 8,
                            "columnCount": 5,
                            "widgets": [
                                {
                                    "_type": "GridWidget",
                                    "identifier": "time_entries_current_user",
                                    "startRow": 1,
                                    "endRow": 8,
                                    "startColumn": 1,
                                    "endColumn": 3
                                },
                                {
                                    "_type": "GridWidget",
                                    "identifier": "news",
                                    "startRow": 3,
                                    "endRow": 8,
                                    "startColumn": 4,
                                    "endColumn": 5
                                },
                                {
                                    "_type": "GridWidget",
                                    "identifier": "documents",
                                    "startRow": 1,
                                    "endRow": 3,
                                    "startColumn": 3,
                                    "endColumn": 6
                                }
                            ],
                            "createdAt": "2018-12-03T16:58:30Z",
                            "updatedAt": "2018-12-13T19:36:40Z",
                            "_links": {
                                "scope": {
                                    "href": "/my/page",
                                    "type": "text/html"
                                },
                                "updateImmediately": {
                                    "href": "/api/v3/grids/2",
                                    "method": "patch"
                                },
                                "update": {
                                    "href": "/api/v3/grids/2/form",
                                    "method": "post"
                                },
                                "self": {
                                    "href": "/api/v3/grids/2"
                                }
                            }
                        },
                        {
                            "_type": "Grid",
                            "id": 5,
                            "rowCount": 1,
                            "columnCount": 4,
                            "widgets": [
                                {
                                    "_type": "GridWidget",
                                    "identifier": "work_package_query",
                                    "startRow": 1,
                                    "endRow": 8,
                                    "startColumn": 1,
                                    "endColumn": 3
                                },
                                {
                                    "_type": "GridWidget",
                                    "identifier": "work_package_query",
                                    "startRow": 3,
                                    "endRow": 8,
                                    "startColumn": 4,
                                    "endColumn": 5
                                },
                                {
                                    "_type": "GridWidget",
                                    "identifier": "work_package_query",
                                    "startRow": 1,
                                    "endRow": 3,
                                    "startColumn": 3,
                                    "endColumn": 6
                                }
                            ],
                            "createdAt": "2019-01-05T16:58:30Z",
                            "updatedAt": "2019-01-07T19:36:40Z",
                            "_links": {
                                "scope": {
                                    "href": "/projects/a_project/boards",
                                    "type": "text/html"
                                },
                                "updateImmediately": {
                                    "href": "/api/v3/grids/5",
                                    "method": "patch"
                                },
                                "update": {
                                    "href": "/api/v3/grids/5/form",
                                    "method": "post"
                                },
                                "self": {
                                    "href": "/api/v3/grids/5"
                                }
                            }
                        }
                    ]
                },
                "_links": {
                    "self": {
                        "href": "/api/v3/grids?offset=1&pageSize=30"
                    },
                    "jumpTo": {
                        "href": "/api/v3/grids?offset=%7Boffset%7D&pageSize=30",
                        "templated": true
                    },
                    "changeSize": {
                        "href": "/api/v3/grids?offset=1&pageSize=%7Bsize%7D",
                        "templated": true
                    }
                }
            }


## List Grids [GET]

Lists all grids matching the provided filters and being part of the selected query page. The grids returned will also depend on the permissions of the requesting user.

+ Parameters
    + offset = `1` (optional, integer, `25`) ... Page number inside the requested collection.

    + pageSize (optional, integer, `25`) ... Number of elements to display per page.

    + filters (optional, string, `[{ "page": { "operator": "=", "values": ["/my/page"] } }]`) ... JSON specifying filter conditions.
    Accepts the same format as returned by the [queries](#queries) endpoint. Currently supported filters are:
      + page: Filter grid by work package

+ Response 200 (application/hal+json)

    [Grid][]

+ Response 400 (application/hal+json)

    Returned if the client sends invalid request parameters e.g. filters

    + Body

            {
                "_type": "Error",
                "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidQuery",
                "message": [
                  "Filters Invalid filter does not exist."
                ]
            }

+ Response 403 (application/hal+json)

    Returned if the client is not logged in and login is required.

    + Body

            {
                "_type": "Error",
                "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
                "message": "You are not authorized to view this resource."
            }

## Create Grid [POST]

Creates a new grid applying the attributes provided in the body. The constraints applied to the grid depend on the page the grid is placed in which is why the create form end point should be used to be guided when wanting to create a grid.

+ Request Create grid

    + Body

            {
                "rowCount": 8,
                "columnCount": 5,
                "widgets": [
                     {
                         "identifier": "time_entries_current_user",
                         "startRow": 1,
                         "endRow": 8,
                         "startColumn": 1,
                         "endColumn": 3
                     },
                     {
                         "identifier": "news",
                         "startRow": 3,
                         "endRow": 8,
                         "startColumn": 4,
                         "endColumn": 5
                     },
                     {
                         "identifier": "documents",
                         "startRow": 1,
                         "endRow": 3,
                         "startColumn": 3,
                         "endColumn": 6
                     }
                ],
                "_links": {
                    "scope": {
                        "href": "/my/page"
                    }
                }
            }

+ Response 201

    [Grid][]

+ Response 400 (application/hal+json)

    Occurs when the client did not send a valid JSON object in the request body.

    + Body

            {
                "_type": "Error",
                 "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody",
                "message": "The request body was not a single JSON object."
            }

+ Response 403 (application/hal+json)

    Returned if the client does not have sufficient permissions.

    **Required permission:** Depends on the page the grid is defined for.

    + Body

            {
                "_type": "Error",
                "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
                "message": "You are not authorized to access this resource."
            }

+ Response 422 (application/hal+json)

    Returned if:

    * a constraint for a property was violated (`PropertyConstraintViolation`)

    + Body

            {
                "_type": "Error",
                "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
                "message": "Grid is invalid.",
                "_embedded": {
                    "details": {
                        "attribute"=>"rowCount",
                        "message"=>"Number of rows must be greater than 0."
                    }
                }
            }

## Update Grid [PATCH]

Updates the given grid by applying the attributes provided in the body.  The constraints applied to the grid depend on the page the grid is placed in which is why the create form end point should be used to be guided when wanting to update a grid.

+ Request Update grid

    + Body

            {
                "rowCount": 8,
                "columnCount": 5,
                "widgets": [
                     {
                         "identifier": "time_entries_current_user",
                         "startRow": 1,
                         "endRow": 8,
                         "startColumn": 1,
                         "endColumn": 3
                     },
                     {
                         "identifier": "news",
                         "startRow": 3,
                         "endRow": 8,
                         "startColumn": 4,
                         "endColumn": 5
                     },
                     {
                         "identifier": "documents",
                         "startRow": 1,
                         "endRow": 3,
                         "startColumn": 3,
                         "endColumn": 6
                     }
                ]
            }

+ Response 200

    [Grid][]

+ Response 400 (application/hal+json)

    Occurs when the client did not send a valid JSON object in the request body.

    + Body

            {
                "_type": "Error",
                 "errorIdentifier": "urn:openproject-org:api:v3:errors:InvalidRequestBody",
                "message": "The request body was not a single JSON object."
            }

+ Response 403 (application/hal+json)

    Returned if the client does not have sufficient permissions.

    **Required permission:** The permission depends on the page the grid is placed in.

    + Body

            {
                "_type": "Error",
                "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
                "message": "You are not authorized to access this resource."
            }

+ Response 422 (application/hal+json)

    Returned if:

    * a constraint for a property was violated (`PropertyConstraintViolation`)

    + Body

            {
                "_type": "Error",
                "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
                "message": "Grid is invalid.",
                "_embedded": {
                    "details": {
                        "attribute"=>"rowCount",
                        "message"=>"Number of rows must be greater than 0."
                    }
                }
            }

## Grid Create Form [/api/v3/grids/form]

This endpoint returns a form to allow a guided creation of a new grids.
The returned form will be pre-filled with default values for every property, if available.

For more details and all possible responses see the general specification of [Forms](/api/forms).
A page link must be provided in the body when calling this end point.

## Grid Create Form [POST]

+ Response 200 (application/hal+json)

    + Body

            {
                "_type": "Form",
                "_embedded": {
                    "payload": {
                        "rowCount": 6,
                        "columnCount": 4,
                        "widgets": [
                            {
                                "_type": "GridWidget",
                                "identifier": "work_packages_assigned",
                                "startRow": 1,
                                "endRow": 7,
                                "startColumn": 1,
                                "endColumn": 3
                            },
                            {
                                "_type": "GridWidget",
                                "identifier": "work_packages_created",
                                "startRow": 1,
                                "endRow": 7,
                                "startColumn": 3,
                                "endColumn": 5
                            }
                        ],
                        "_links": {
                            "scope": {
                                "href": "/my/page",
                                "type": "text/html"
                            }
                        }
                    },
                    "schema": {
                        "_type": "Schema",
                        "id": {
                            "type": "Integer",
                            "name": "ID",
                            "required": true,
                            "hasDefault": false,
                            "writable": false
                        },
                        "createdAt": {
                            "type": "DateTime",
                            "name": "Created on",
                            "required": true,
                            "hasDefault": false,
                            "writable": false
                        },
                        "updatedAt": {
                            "type": "DateTime",
                            "name": "Updated on",
                            "required": true,
                            "hasDefault": false,
                            "writable": false
                        },
                        "rowCount": {
                            "type": "Integer",
                            "name": "Number of rows",
                            "required": true,
                            "hasDefault": false,
                            "writable": true
                        },
                        "columnCount": {
                            "type": "Integer",
                            "name": "Number of columns",
                            "required": true,
                            "hasDefault": false,
                            "writable": true
                        },
                        "scope": {
                            "type": "Href",
                            "name": "Page",
                            "required": true,
                            "hasDefault": false,
                            "writable": true,
                            "_links": {
                                "allowedValues": [
                                    {
                                        "href": "/my/page",
                                        "title": "My page"
                                    }
                                ]
                            }
                        },
                        "widgets": {
                            "type": "[]GridWidget",
                            "name": "Widgets",
                            "required": true,
                            "hasDefault": false,
                            "writable": true,
                            "_embedded": {
                                allowedValues: [
                                    {
                                        "_type": "GridWidget",
                                        "identifier": "work_packages_assigned"
                                    },
                                    {
                                        "_type": "GridWidget",
                                        "identifier": "news"
                                    }
                                ]
                            },
                            "_links": {}
                        },
                        "_links": {}
                    },
                    "validationErrors": {
                        "widgets": {
                            "_type": "Error",
                            "errorIdentifier": "urn:openproject-org:api:v3:errors:MultipleErrors",
                            "message": "Multiple field constraints have been violated.",
                            "_embedded": {
                                "errors": [
                                    {
                                        "_type": "Error",
                                        "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
                                        "message": "Widgets is outside of the grid.",
                                        "_embedded": {
                                            "details": {
                                                "attribute": "widgets"
                                            }
                                        }
                                    },
                                    {
                                        "_type": "Error",
                                        "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
                                        "message": "Widgets is outside of the grid.",
                                        "_embedded": {
                                            "details": {
                                                "attribute": "widgets"
                                            }
                                        }
                                    }
                                ]
                            }
                        },
                        "rowCount": {
                            "_type": "Error",
                            "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
                            "message": "Number of rows must be greater than 0.",
                            "_embedded": {
                                "details": {
                                    "attribute": "rowCount"
                                }
                            }
                        }
                    }
                },
                "_links": {
                    "self": {
                        "href": "/api/v3/grids/form",
                        "method": "post"
                    },
                    "validate": {
                        "href": "/api/v3/grids/form",
                        "method": "post"
                    }
                }
            }


## Grid Update Form [/api/v3/grids/{id}/form]

This endpoint returns a form to allow a guided modification of an existing grid.

For more details and all possible responses see the general specification of [Forms](/api/forms).

## Grid Update Form [POST]

+ Parameters
    + id (required, integer, `1`) ... ID of the grid being modified

+ Request Update grid form

    + Body

            {
                "rowCount": 8,
                "columnCount": 5,
                "widgets": [
                     {
                         "identifier": "time_entries_current_user",
                         "startRow": 1,
                         "endRow": 8,
                         "startColumn": 1,
                         "endColumn": 3
                     },
                     {
                         "identifier": "news",
                         "startRow": 3,
                         "endRow": 8,
                         "startColumn": 4,
                         "endColumn": 5
                     },
                     {
                         "identifier": "documents",
                         "startRow": 1,
                         "endRow": 3,
                         "startColumn": 3,
                         "endColumn": 6
                     }
                ]
            }

+ Response 200 (application/hal+json)

    + Body

            {
                "_type": "Form",
                "_embedded": {
                    "payload": {
                        "rowCount": 6,
                        "columnCount": 5,
                        "widgets": [
                            {
                                "_type": "GridWidget",
                                "identifier": "time_entries_current_user",
                                "startRow": 1,
                                "endRow": 8,
                                "startColumn": 1,
                                "endColumn": 3
                            },
                            {
                                "_type": "GridWidget",
                                "identifier": "news",
                                "startRow": 3,
                                "endRow": 8,
                                "startColumn": 4,
                                "endColumn": 5
                            },
                            {
                                "_type": "GridWidget",
                                "identifier": "documents",
                                "startRow": 1,
                                "endRow": 3,
                                "startColumn": 3,
                                "endColumn": 6
                            }
                        ]
                    },
                    "schema": {
                        "_type": "Schema",
                        "id": {
                            "type": "Integer",
                            "name": "ID",
                            "required": true,
                            "hasDefault": false,
                            "writable": false
                        },
                        "createdAt": {
                            "type": "DateTime",
                            "name": "Created on",
                            "required": true,
                            "hasDefault": false,
                            "writable": false
                        },
                        "updatedAt": {
                            "type": "DateTime",
                            "name": "Updated on",
                            "required": true,
                            "hasDefault": false,
                            "writable": false
                        },
                        "rowCount": {
                            "type": "Integer",
                            "name": "Number of rows",
                            "required": true,
                            "hasDefault": false,
                            "writable": true
                        },
                        "columnCount": {
                            "type": "Integer",
                            "name": "Number of columns",
                            "required": true,
                            "hasDefault": false,
                            "writable": true
                        },
                        "scope": {
                            "type": "Href",
                            "name": "Page",
                            "required": true,
                            "hasDefault": false,
                            "writable": false,
                            "_links": {}
                        },
                        "widgets": {
                            "type": "[]GridWidget",
                            "name": "Widgets",
                            "required": true,
                            "hasDefault": false,
                            "writable": true,
                            "_links": {}
                        },
                        "_links": {}
                    },
                    "validationErrors": {
                        "widgets": {
                            "_type": "Error",
                            "errorIdentifier": "urn:openproject-org:api:v3:errors:MultipleErrors",
                            "message": "Multiple field constraints have been violated.",
                            "_embedded": {
                                "errors": [
                                    {
                                        "_type": "Error",
                                        "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
                                        "message": "Widgets is outside of the grid.",
                                        "_embedded": {
                                            "details": {
                                                "attribute": "widgets"
                                            }
                                        }
                                    },
                                    {
                                        "_type": "Error",
                                        "errorIdentifier": "urn:openproject-org:api:v3:errors:PropertyConstraintViolation",
                                        "message": "Widgets is outside of the grid.",
                                        "_embedded": {
                                            "details": {
                                                "attribute": "widgets"
                                            }
                                        }
                                    }
                                ]
                            }
                        }
                    }
                },
                "_links": {
                    "self": {
                        "href": "/api/v3/grids/2/form",
                        "method": "post"
                    },
                    "validate": {
                        "href": "/api/v3/grids/2/form",
                        "method": "post"
                    }
                }
            }

+ Response 403 (application/hal+json)

    Returned if the client does not have sufficient permissions.

    **Required permission:** depends on the page the grid is defined for.

    *Note that you will only receive this error, if you are at least allowed to see the corresponding grid.*

    + Body

            {
                "_type": "Error",
                "errorIdentifier": "urn:openproject-org:api:v3:errors:MissingPermission",
                "message": "You are not authorized to access this resource."
            }

+ Response 404 (application/hal+json)

    Returned if the grid does not exist or the client does not have sufficient permissions to see it.

    **Required permission:** view work package

    + Body

            {
                "_type": "Error",
                "errorIdentifier": "urn:openproject-org:api:v3:errors:NotFound",
                "message": "The requested resource could not be found."
            }

